#include <xbi/xbipfi.h>

#if 0
#pragma mark Definitions
#endif

struct _XBIPhysicalFormatInformation{
    byte_t * data;
    int      size;
    
    int      layerCount;
    int32_t  startPSN; // Sectors can be stored on 32 bits
    int32_t  endPSN;
    int32_t  layer0LastSector;
    
};

#if 0
#pragma mark Creation - Copy - Deletion
#endif

XBIPhysicalFormatInformation * XBIPFICreateFromBuffer(XBIPFIVersion version, byte_t * buffer, int size, XBIError * error) {
    
    XBIPhysicalFormatInformation * newPFI = NULL;
    byte_t bigEndianBuffer[4];
    
    if (XBIBufferIsEmpty(buffer, size)) {
        XBILog("Physical format information buffer is empty, ignoring\n");
        *error = kXBIFileWrongFormat;
        goto error;
    }
    
    XBILog("Checking PFI buffer for integrity\n");

    if (size != _XBIPhysicalFormatInformationSize) {
        XBILog("Physical format information buffer doesn't have the right size\n");
        *error = kXBIFileWrongFormat;
        goto error;
    }
    
    XBILog("PFI buffer has correct length\n");
    
    newPFI = malloc(sizeof(XBIPhysicalFormatInformation));
    if (newPFI == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;
    }
    memset(newPFI, 0, sizeof(XBIPhysicalFormatInformation));
    
    newPFI->data = malloc(_XBIPhysicalFormatInformationSize);
    if (newPFI->data == NULL) {
        *error = kXBINotEnoughMemory;
        goto error;
    }
    memcpy(newPFI->data, buffer, _XBIPhysicalFormatInformationSize);
    
    newPFI->size = size;
    
    newPFI->layerCount = ((newPFI->data[2] & 0x60) >> 5) + 1; // Read the right bits
    
    bigEndianBuffer[0] = 0;
    memcpy(bigEndianBuffer+1, newPFI->data + 5, 3);
    newPFI->startPSN = XBIReadBigEndianInt32AtAddress(bigEndianBuffer);
    XBILog("Start PSN = 0x%x\n", newPFI->startPSN);
    
    
    memcpy(bigEndianBuffer+1, newPFI->data + 9, 3);
    newPFI->endPSN = XBIReadBigEndianInt32AtAddress(bigEndianBuffer);
    XBILog("End PSN = 0x%x\n", newPFI->endPSN);
    memcpy(bigEndianBuffer+1, newPFI->data + 13, 3);
    newPFI->layer0LastSector = XBIReadBigEndianInt32AtAddress(bigEndianBuffer);
    XBILog("Layer 0 last sector = 0x%x\n", newPFI->layer0LastSector);    
    
    if (version == kXBIPFIXbox360Version) {
        
        if (newPFI->startPSN != _XBIXbox360StartPSN) {
            XBILog("Not a valid Xbox360 PFI size (Wrong start PSN)\n");
            *error = kXBIFileWrongFormat;
            goto error;
        }
        
        XBILog("Video size from PFI: %lld\n", XBIPFIGetDiscSize(newPFI));
    }
    
    XBILog("PFI loaded OK\n");
    
    return newPFI;
    
error:
    XBIPFIDelete(newPFI);
    return NULL;
}

XBIPhysicalFormatInformation * XBIPFICreateFromFile(XBIPFIVersion version, FILE * file, XBIError * error) {
    size_t bufferSize = 0;
    byte_t * buffer = XBICreateBufferFromFile(file, &bufferSize);
    return XBIPFICreateFromBuffer(version, buffer, bufferSize, error);
}

XBIPhysicalFormatInformation * XBIPFICopy(XBIPhysicalFormatInformation * pfi) {
    // TODO: Real implementation of Copy
    return pfi;
}

void XBIPFIDelete(XBIPhysicalFormatInformation * pfi) {
    if (pfi != NULL) {
        if (pfi->data != NULL) {
            free(pfi->data);
            pfi->data = NULL;
        }
        free(pfi);
    }
}

#if 0
#pragma mark Data manipulation
#endif

int32_t XBIPFIGetLayer0SizeInSectors(XBIPhysicalFormatInformation * pfi) {
    if (pfi == NULL)
        return -1;
    // +1 cause end psn is last used sector, not last sector total
    return 1 + (pfi->layer0LastSector - pfi->startPSN);
}

int32_t XBIPFIGetLayer1SizeInSectors(XBIPhysicalFormatInformation * pfi) {
    if (pfi == NULL)
        return -1;
    // +1 cause end psn is last used sector, not last sector total
    return 1 + (pfi->endPSN - (int32_t)((unsigned int)~pfi->layer0LastSector ^ 0xff000000));
}

off_t XBIPFIGetDiscSize(XBIPhysicalFormatInformation * pfi) {
        return kXBIDVDSectorSize * ((off_t)XBIPFIGetLayer0SizeInSectors(pfi) + (off_t)XBIPFIGetLayer1SizeInSectors(pfi));
}

uint32_t XBIPFIGetCRC32(XBIPhysicalFormatInformation * pfi) {
    if (pfi == NULL || pfi->data == NULL || pfi->size == 0)
            return 0;
    return XBIComputeCRC32(pfi->data, pfi->size);
}

#if 0
#pragma mark Accessors
#endif

//Accessors
byte_t * XBIPFIGetData(XBIPhysicalFormatInformation * pfi) {
    if (pfi == NULL)
        return NULL;
    return pfi->data;
}

size_t XBIPFIGetSize(XBIPhysicalFormatInformation * pfi) {
    if (pfi == NULL)
        return -1;
    return pfi->size;    
}

int32_t XBIPFIGetStartPSN(XBIPhysicalFormatInformation * pfi) {
    if (pfi == NULL)
        return -1;
    return pfi->startPSN;
}
